//建议文档的索引（正排和倒排）
//单例对象
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <unordered_map>
#include <fstream>
#include <mutex>
#include "util.hpp"
#include"log.hpp"

namespace ns_index
{
    typedef struct DocInfo
    {
        std::string _title;
        std::string _body;
        std::string _url;
        uint64_t _id;
    } DocInfo_t;

    struct invertedElem
    {
        uint64_t _id;      //方便找到id
        std::string _word; //单词
        int _weight;       //相关性,10*标题+正文
    };

    typedef std::vector<invertedElem> invertedList; //倒排拉链

    class index
    {
    public:
        DocInfo_t *getForwardElem(uint64_t id) //获取正排索引的元素
        {
            if (id >= forwardList.size())
            {
                LOG(ERROR,"id out of range");
                return nullptr;
            }
            return &forwardList[id];
        }

        invertedList *getInvertedElem(const std::string &word) //获取倒排拉链
        {
            if (invertedIndex.count(word) == 0)
            {
                LOG(ERROR,"word not found");
                return nullptr;
            }
            return &invertedIndex[word];
        }

        bool buildIndex(const std::string &input) //构造索引表
        // input首先获取文件路径
        {
            LOG(NORMAL,"start build index");
            std::ifstream ifs(input, std::ios::in | std::ios::binary);

            if (!ifs.is_open())
            {
                LOG(ERROR,"open input failed!");
                return false;
            }

            std::string line;
            int cnt = 0;
            while (std::getline(ifs, line))
            {
                //正排索引
                DocInfo_t *doc = buildForward(line);
                if (!doc)
                {
                    LOG(ERROR,"build forward error!");
                    return false;
                }
                //根据正排索引建立倒排索引
                buildInverted(*doc);
                cnt++;
                std::string logg="build progress";
                logg+=std::to_string(cnt);
                LOG(NORMAL,logg);
                
            }

            return true;
        }
        ~index() {}

        static index *getInstance()
        {
            if (!_instance)
            {
                _mtx.lock();
                if (!_instance)
                {
                    _instance = new index();
                }
                _mtx.unlock();
            }
            return _instance;
        }

    private:
        std::vector<DocInfo_t> forwardList;                          //正排索引，利用数组下标建立
        std::unordered_map<std::string, invertedList> invertedIndex; //倒排索引
        //一个词可能对应多个文档，所以需要数组保存
        static index *_instance;
        static std::mutex _mtx;

        index() {}

        index(const index &) = delete;

        index &operator=(const index &) = delete;

        DocInfo_t *buildForward(const std::string &line)
        {
            const std::string sep = "\3";
            DocInfo_t cur;
            //首先将文件切分为title,body,url
            std::vector<std::string> splited;
            ns_util::stringUtil::cutString(line, &splited, sep);
            if (splited.size() != 3)
            {
                LOG(ERROR,"split error!");
                return nullptr;
            }
            cur._title = splited[0];
            cur._body = splited[1];
            cur._url = splited[2];
            cur._id = forwardList.size();
            forwardList.push_back(std::move(cur));
            return &forwardList.back();
        }

        bool buildInverted(const DocInfo_t &doc)
        {
            //倒排索引：根据关键词来找到文档
            //首先利用jieba库来切分文档中的关键词
            
            struct wordCnt
            {
                wordCnt()
                    : _titleCnt(0), _bodyCnt(0)
                {
                }
                int _titleCnt;
                int _bodyCnt;
            };

            //切分标题

            std::vector<std::string> splitedTitle;
            ns_util::jieBaUtil::curString(doc._title, &splitedTitle);

            //切分正文

            std::vector<std::string> splitedBody;
            ns_util::jieBaUtil::curString(doc._body, &splitedBody);
            std::unordered_map<std::string, wordCnt> wordMap;
            //词频统计——标题
            for (auto &s : splitedTitle)
            {
                boost::to_lower(s);
                wordMap[s]._titleCnt++;
            }

            //词频统计——正文
            for (auto &s : splitedBody)
            {
                boost::to_lower(s);
                wordMap[s]._bodyCnt++;
            }
            
            for (auto &p : wordMap)
            {
                invertedElem ie; //将这个单词的信息插入到我们的倒排索引中
                const int x = 10;
                const int y = 1;
                ie._id = doc._id;
                ie._word = p.first;
                ie._weight = x * (p.second._titleCnt) + y * (p.second._bodyCnt);
                invertedList &ivl = invertedIndex[p.first]; //返回的是一个哈希表的引用
                //如果没有元素将会自动创建
                ivl.push_back(std::move(ie)); //插入这个单词在这个文档中的信息
            }
            return true;
        }
    };

    index *index::_instance = nullptr;
    std::mutex index::_mtx;

} // namespace ns_index
